#!/usr/bin/env python -O

"""
Defines a series of symbolic name groups which allow us to represent a context
in an HTML document as a number.
"""


def state_of(context):
    """
    The STATE_* portion of the given context.
    The state is a coarse-grained description of the parser state.
    """
    return context & STATE_ALL

def is_error_context(context):
    """True iff the state of the context is STATE_ERROR."""
    return state_of(context) == STATE_ERROR

# Outside an HTML tag, directive, or comment.  (Parsed character data).
STATE_TEXT = 0

# Inside an element whose content is RCDATA where text and entities
# can appear but where nested elements cannot.
# The content of "<title>" and "<textarea>" fall into
# this category since they cannot contain nested elements in HTML.
STATE_RCDATA = 1

# Just before a tag name.
STATE_HTML_BEFORE_TAG_NAME = 2

# Inside a tag name.
STATE_TAG_NAME = 3

# Before an HTML attribute or the end of a tag.
STATE_TAG = 4

# Inside an HTML attribute name.
STATE_ATTR_NAME = 5

STATE_AFTER_NAME = 6

# Following an equals sign ('=') after an attribute name in an HTML tag.
STATE_BEFORE_VALUE = 7

# Inside an HTML comment.
STATE_HTMLCMT = 8

# Inside a normal (non-CSS, JS, or URL) HTML attribute value.
STATE_ATTR = 9

# In CSS content outside a comment, string, or URL.
STATE_CSS = 10

# In CSS inside a // line comment.
STATE_CSSLINE_CMT = 11

# In CSS inside a /* block comment */.
STATE_CSSBLOCK_CMT = 12

# In CSS inside a "double quoted string".
STATE_CSSDQ_STR = 13

# In CSS inside a 'single quoted string'.
STATE_CSSSQ_STR = 14

# In CSS in a URL terminated by the first close parenthesis.
STATE_CSS_URL = 15

# In CSS in a URL terminated by the first double quote.
STATE_CSSDQ_URL = 16

# In CSS in a URL terminated by the first single quote.
STATE_CSSSQ_URL = 17

# In JavaScript, outside a comment, string, or Regexp literal.
STATE_JS = 18

# In JavaScript inside a // line comment.
STATE_JSLINE_CMT = 19

# In JavaScript inside a /* block comment */.
STATE_JSBLOCK_CMT = 20

# In JavaScript inside a "double quoted string".
STATE_JSDQ_STR = 21

# In JavaScript inside a 'single quoted string'.
STATE_JSSQ_STR = 22

# In JavaScript inside a /regular expression literal/.
STATE_JSREGEXP = 23

# In an HTML attribute whose content is a URL.
STATE_URL = 24

# Not inside any valid HTML/CSS/JS construct.
STATE_ERROR = 25

COUNT_OF_STATES = 26

# All of the state bits set.
STATE_ALL = 31


def element_type_of(context):
    """
    The ELEMENT_* portion of context.
    These values describe the type of HTML element in which context appears.
    """
    return context & ELEMENT_ALL

# No element or not a special element.
ELEMENT_NONE = 0

# A script element whose content is raw JavaScript.
ELEMENT_SCRIPT = 1 << 5

# A style element whose content is raw CSS.
ELEMENT_STYLE = 2 << 5

# A textarea element whose content is encoded HTML but which cannot contain
# elements.
ELEMENT_TEXTAREA = 3 << 5

# A title element whose content is encoded HTML but which cannot contain
# elements.
ELEMENT_TITLE = 4 << 5

# A listing element whose content is raw CDATA.
ELEMENT_LISTING = 5 << 5

# An XMP element whose content is raw CDATA.
ELEMENT_XMP = 6 << 5

# A close tag ("</foo>") which does not start an element.
ELEMENT_CLOSE = 7 << 5

# All of the element bits set.
ELEMENT_ALL = 7 << 5


def attr_type_of(context):
    """
    The ATTR_* portion of context.
    These values describe the content of the HTML attribute in which the
    context appears.
    """
    return context & ATTR_ALL

# No attribute or an attribute whose context is human readable or other
# non-structured plain text or keyword values.
ATTR_NONE = 0

# Mime-type text/javascript.
ATTR_SCRIPT = 1 << 8

# Mime-type text/css.
ATTR_STYLE = 2 << 8

# A URL or URL reference.
ATTR_URL = 3 << 8

# All of the attribute type bits set.
ATTR_ALL = 3 << 8


def delim_type_of(context):
    """
    The DELIM_* portion of context.

    These values describe the content that will end the HTML attribute in
    which context appears.
    """
    return context & DELIM_ALL

# Not in an attribute.
DELIM_NONE = 0

# "
DELIM_DOUBLE_QUOTE = 1 << 10

# '
DELIM_SINGLE_QUOTE = 2 << 10

# A space or '>' symbol.
DELIM_SPACE_OR_TAG_END = 3 << 10

# All of the delimiter type bits set.
DELIM_ALL = 3 << 10

# Maps DELIM_* to the text used to delimit attributes of that type.
DELIM_TEXT = {
    DELIM_DOUBLE_QUOTE: '"',
    DELIM_SINGLE_QUOTE: "'",
    DELIM_SPACE_OR_TAG_END: "",
    }


def js_ctx_of(context):
    """
    The JS_CTX_* portion of context.

    These values describes what a slash ('/') means when parsing JavaScript
    source code.  A slash that is not followed by another slash or an
    asterisk ('*') can either start a regular expression literal
    or start a division operator.

    This determination is made based on the full grammar, but Waldemar
    defined a very close to accurate grammar for a JavaScript 1.9 draft
    based purely on a regular lexical grammar which is what we use in
    the autoescaper.

    See also context_update.is_regex_preceder
    """
    return context & JS_CTX_ALL

# Not in JavaScript or in a context where the next token would start a regular
# expression literal.
JS_CTX_REGEX = 0 << 12

# A slash as the next token would start a division operator.
JS_CTX_DIV_OP = 1 << 12

# We do not know what a slash as the next token would start so it is
# an error for the next token to be a slash.
JS_CTX_UNKNOWN = 2 << 12

# All of the JS following slash bits set.
JS_CTX_ALL = 3 << 12


def url_part_of(context):
    """
    The URL_PART_* portion of context.

    These values describe the part of a URL reference in which context occurs.

    We need to distinguish these so that we can:
    1. normalize well-formed URIs that appear before the query,
    2. encode raw values interpolated as query parameters or keys,
    3. filter out values that specify a scheme like \"javascript:\".
    """
    return context & URL_PART_ALL

# Not in a URL or at the start, the ^ in "^http://auth/path?k=v#frag"..
URL_PART_NONE = 0

# In the scheme, authority, or path.
# Between ^s in "h^ttp://host/path^?k=v#frag".
URL_PART_PRE_QUERY = 1 << 14

# In the query portion.  Between ^s in "http://host/path?^k=v#frag^".
URL_PART_QUERY_OR_FRAG = 2 << 14

# In a URL, but not clear where.  Used to join different contexts.
URL_PART_UNKNOWN = 3 << 14

# All of the URL part bits set.
URL_PART_ALL = 3 << 14


_PARTIAL_CONTEXT_FOR_ATTR = {
    ATTR_NONE: STATE_ATTR,
    # Start a JS block in a regex state since
    #   /foo/.test(str) && doSideEffect();
    # which starts with a regular expression literal is a valid and possibly
    # useful program, but there is no valid program which starts with a
    # division operator.
    ATTR_SCRIPT: STATE_JS | JS_CTX_REGEX,
    ATTR_STYLE: STATE_CSS,
    ATTR_URL: STATE_URL | URL_PART_NONE,
    }


def after_attr_delimiter(el_type, attr_type, delim):
    """
    Returns the context after an attribute delimiter for the given element
    type, attribute type, and delimiter type.
    """
    return _PARTIAL_CONTEXT_FOR_ATTR[attr_type] | el_type | delim


def force_epsilon_transition(context):
    """
    Some epsilon transitions need to be delayed until we get into a branch.
    For example, we do not transition into an unquoted attribute value
    context just because the raw text node that contained the "=" did
    not contain a quote character because the quote character may appear
    inside branches as in
        <a href={{if ...}}"..."{{else}}"..."{{/if}}>
    which was derived from production code.

    Parsing:
        <a href=
    will end in context STATE_BEFORE_VALUE | ATTR_URL, but parsing another char:
        <a href=x
    will end in context STATE_URL | DELIM_SPACE_OR_TAG_END | ...
    There are two transitions that happen when the 'x' is seen:
    (1) Transition from a before-value state to a start-of-value state without
        consuming any character.
    (2) Consume 'x' and transition past the first value character.
    In this case, nudging produces the context after (1) happens.

    We need to force epsilon transitions to happen consistently before
    a dynamic value is considered as in
        <a href=${x}>
    where we consider $x as happening in an unquoted attribute value context,
    not as occuring before an attribute value.
    """

    state = state_of(context)
    if state in (STATE_TAG, STATE_TAG_NAME):
        # In "<foo {{.}}", the hole should be filled with an attribute.
        context = (context & ~STATE_ALL) | STATE_ATTR_NAME
    elif state == STATE_BEFORE_VALUE:
        # In "<foo bar={{.}}", the hole should be filled with an unquoted
        # value.
        context = after_attr_delimiter(
            element_type_of(context), attr_type_of(context),
            DELIM_SPACE_OR_TAG_END)
    elif state == STATE_AFTER_NAME:
        # In "<foo bar {{.}}", the hole should be filled with an attribute name.
        context = (context & ~(STATE_ALL | ATTR_ALL)) | (
            STATE_ATTR_NAME | ATTR_NONE)
    return context
